package servlet;

import annotation.RequestMapping;
import com.alibaba.fastjson.JSON;
import lombok.SneakyThrows;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @author Kevin
 * @date 2020-12-29 19:15
 */
@WebServlet("/*")
public class BaseServlet extends HttpServlet {

    private static final String CONTROLLER = "controller";
    private static final String SUFFIX = ".class";
    private static final String DOT = ".";
    private static final String SPACE = " ";
    private static final String EMPTY_STR = "";

    Map<String, Method> requestMapping;
    Map<Method, Object> instanceMapping;

    /**
     * 首次请求时，初始化servlet
     * 扫描controller包，创建对象，准备方法映射
     * 暂不支持子包
     */
    @Override
    public void init() {
        this.requestMapping = new HashMap<>(16);
        this.instanceMapping = new HashMap<>(16);
        URL controllerUrl = Thread.currentThread().getContextClassLoader().getResource(CONTROLLER);
        assert controllerUrl != null;
        File file = new File(controllerUrl.getFile());
        for (String className : Objects.requireNonNull(file.list())) {
            if (className.contains(SUFFIX)) {
                className = className.replace(SUFFIX, EMPTY_STR);
                // 全限定类名
                String classReference = CONTROLLER + DOT + className;
                try {
                    Class<?> aClass = Class.forName(classReference);
                    Object instance = aClass.getConstructor().newInstance();
                    Method[] declaredMethods = aClass.getDeclaredMethods();
                    for (Method declaredMethod : declaredMethods) {
                        RequestMapping annotation = declaredMethod.getAnnotation(RequestMapping.class);
                        if (annotation != null) {
                            this.requestMapping.put(annotation.method() + SPACE + annotation.value(), declaredMethod);
                            this.instanceMapping.put(declaredMethod, instance);
                        }
                    }
                } catch (Exception e) {
                    System.out.println("找不到类：" + classReference);
                    e.printStackTrace();
                }
            }
        }
    }

    @SneakyThrows
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) {
        String requestUri = req.getRequestURI().replace(req.getServletPath(), EMPTY_STR);
        Method method = requestMapping.get(req.getMethod() + SPACE + requestUri);
        Object responseObj;
        if (method != null) {
            Object instance = instanceMapping.get(method);
            responseObj = invokeHandle(instance, method, requestUri, req, resp, false);
        } else {
            // 假设匹配不上就是占位符url，只支持整数
            int index = requestUri.lastIndexOf('/');
            method = requestMapping.get(req.getMethod() + SPACE + requestUri.substring(0, index) + "/{id}");
            if (method != null) {
                Object instance = instanceMapping.get(method);
                responseObj = invokeHandle(instance, method, requestUri, req, resp, true);
            } else {
                throw new RuntimeException("找不到controller...");
            }
        }
        byte[] bytes = JSON.toJSONBytes(responseObj);
        resp.setContentType("application/json");
        resp.getOutputStream().write(bytes);
    }

    /**
     * 封装参数，调用controller方法
     * 目前支持 request/response/单个占位符/单个json对象 封装
     * @param instance controller实例
     * @param method controller方法
     * @param requestUri 请求uri
     * @param req 请求对象
     * @param resp 响应对象
     * @param suffix 是否是占位符url，暂时只支持尾部占位符
     **/
    private Object invokeHandle(Object instance, Method method, String requestUri,
                                HttpServletRequest req, HttpServletResponse resp, boolean suffix) throws Exception {
        Parameter[] parameters = method.getParameters();
        Object[] params = new Object[parameters.length];
        // 封装参数
        for (int i = 0; i < parameters.length; i++) {
            if (parameters[i].getType().equals(HttpServletRequest.class)) {
                params[i] = req;
            } else if (parameters[i].getType().equals(HttpServletResponse.class)) {
                params[i] = resp;
            } else {
                if (suffix) {
                    int integer = Integer.parseInt(requestUri.substring(requestUri.lastIndexOf('/') + 1));
                    params[i] = integer;
                } else if("POST".equals(req.getMethod()) || "PUT".equals(req.getMethod())) {
                    Object paramObj = JSON.parseObject(req.getInputStream(), parameters[i].getType());
                    params[i] = paramObj;
                } else {
                    params[i] = null;
                }
            }
        }
        return method.invoke(instance, params);
    }

}
